package code

import (
	"fmt"
	"log"
	"os"
	"path/filepath"
	"runtime"
	"strings"
	"text/template"
	"upper.io/db.v3/lib/sqlbuilder"
	"upper.io/db.v3/mysql"
)

type CodeGenerator struct {
	Settings        mysql.ConnectionURL
	Tables          []string
	DbName          string
	SrcRootDir      string
	TemplateDir     string
	realTemplateDir string
	db              sqlbuilder.Database
}

func (g *CodeGenerator) Gen() {
	db, err := mysql.Open(g.Settings)

	if err != nil {
		fmt.Println(err)
		panic(err)
	}

	g.db = db

	files := g.readTemplateFiles()
	for _, f := range files {
		t := template.Must(template.ParseFiles(f))
		g.genTables(t, f)

	}

}
func (g *CodeGenerator) genTables(t *template.Template, tf string) {
	for _, tbname := range g.Tables {
		tn := getTableEntity(g.db, g.DbName, tbname)
		tn.PackageName = tn.TableName
		for _, t0 := range t.Templates() {
			g.writeToFile(tn, tf, t0)
		}
	}
}
func (g *CodeGenerator) readTemplateFiles() []string {
	list := make([]string, 0)
	path := g.TemplateDir
	if path == "" {
		path = "./template/"
	}
	if !strings.HasPrefix(path, "/") {
		//获取当前函数Caller reports，取得当前调用对应的文件
		_, f, _, _ := runtime.Caller(0)
		//解析出所在目录
		dir := filepath.Dir(f)
		//组装用于测试的mysql配置文件
		path = filepath.Join(dir, path)
	}

	//fmt.Println(filepath.Abs(path))
	filepath.Walk(path, func(path string, f os.FileInfo, err error) error {
		if f == nil {
			return err
		}
		if !f.IsDir() {
			if filepath.Ext(path) == ".tmpl" {
				list = append(list, path)
				fmt.Println("read template file: ", path)
			}

			return nil
		}
		//fmt.Println(path)
		return nil
	})
	g.realTemplateDir = path
	fmt.Println("real template dir: ", path)
	//fmt.Println(list)
	return list
}

func (g *CodeGenerator) writeToFile(tn *TableEntity, tf string, t *template.Template) {

	tname := t.ParseName
	name := tname
	for i := len(tname) - 1; i >= 0 && !os.IsPathSeparator(tname[i]); i-- {
		if tname[i] == '.' {
			name = tname[:i]
		}
	}
	dir := filepath.Dir(tf)
	dir = strings.TrimPrefix(dir, g.realTemplateDir)
	appendName := ""
	if strings.HasSuffix(dir, "{}") {
		dir = dir[:len(dir)-2]
		appendName = tn.TableName
	}

	file := filepath.Join(g.SrcRootDir, dir, appendName, name+"_"+tn.TableName+".go")
	p, _ := filepath.Abs(file)

	fmt.Println("create file: ", p)
	os.MkdirAll(filepath.Dir(p), os.ModePerm)
	//os.Remove(p)
	f, err := os.OpenFile(p, os.O_RDWR|os.O_CREATE|os.O_APPEND, os.ModePerm)

	//err = t.Templates()[0].Execute(f, tn)

	err = t.ExecuteTemplate(f, tname, tn)

	if err != nil {
		log.Println("executing template:", err)
	}
	f.Close()
}

func getTableEntity(db sqlbuilder.Database, dbname string, tbname string) *TableEntity {

	var fs []FieldInfo
	err := db.Collection("information_schema.COLUMNS").Find("TABLE_NAME=? and table_schema=?", tbname, dbname).OrderBy("ORDINAL_POSITION").All(&fs)
	if err != nil {
		fmt.Println(err)
		panic(err)
	}

	if len(fs) > 0 {
		structName := fmtFieldDefine(tbname)
		tn := &TableEntity{
			TableName:  tbname,
			StructName: structName,
			VarName:    firstUpper(structName),
			Props:      make([]Prop, 0),
			Uniques:    make([]Prop, 0),
		}
		for _, v := range fs {
			propName := fmtFieldDefine(v.ColName)
			prop := Prop{
				ColName:    v.ColName,
				ColComment: v.ColComment,
				DataType:   v.DataType,
				IsNullable: v.IsNullable == "yes",
				PropName:   propName,
				VarName:    firstUpper(propName),
				IsPk:       v.ColumnKey == "PRI",
			}

			switch v.DataType {
			case "int", "tinyint", "smallint":
				if v.IsNullable == "YES" {
					prop.PropType = "sql.NullInt64 "

				} else {
					prop.PropType = "int "
				}
				prop.PropTypeOrigin = "int "
			case "bigint":
				if v.IsNullable == "YES" {
					prop.PropType = "sql.NullInt64 "
				} else {
					prop.PropType = "int64 "
				}
				prop.PropTypeOrigin = "int64 "
			case "char", "varchar", "longtext", "text", "tinytext":
				if v.IsNullable == "YES" {
					prop.PropType = "sql.NullString "
				} else {
					prop.PropType = "string "
				}
				prop.PropTypeOrigin = "string "
			case "date", "datetime", "timestamp":
				prop.PropType = "time.Time "
			case "double", "float":
				if v.IsNullable == "YES" {
					prop.PropType = "sql.NullFloat64 "
				} else {
					prop.PropType = "float64 "
				}
				prop.PropTypeOrigin = "float64 "
			case "decimal", "numeric":
				if v.IsNullable == "YES" {
					prop.PropType = "*decimal.Decimal "
				} else {
					prop.PropType = "decimal.Decimal "
				}
				prop.PropTypeOrigin = "decimal.Decimal "
			default:
				// 其他类型当成string处理
				if v.IsNullable == "YES" {
					prop.PropType = "sql.NullString "
				} else {
					prop.PropType = "string "
				}
				prop.PropTypeOrigin = "string "
			}
			if v.ColumnKey == "PRI" {
				tn.Id = prop
			}
			if v.ColumnKey == "UNI" {
				tn.Uniques = append(tn.Uniques, prop)
			}
			tn.Props = append(tn.Props, prop)
		}
		return tn
	} else {
		fmt.Println("查询不到数据")
	}
	return nil

}

type FieldInfo struct {
	ColName    string `db:"COLUMN_NAME"`
	DataType   string `db:"DATA_TYPE"`
	ColComment string `db:"COLUMN_COMMENT"`
	IsNullable string `db:"IS_NULLABLE"`
	ColumnKey  string `db:"COLUMN_KEY"`
}

type TableEntity struct {
	PackageName string
	TableName   string
	StructName  string
	VarName     string
	Id          Prop
	Props       []Prop
	Uniques     []Prop
}

type Prop struct {
	ColName    string
	DataType   string
	ColComment string
	IsNullable bool
	IsPk       bool

	PropName       string
	VarName        string
	PropType       string
	PropTypeOrigin string
}

func fmtFieldDefine(src string) string {
	temp := strings.Split(src, "_") // 有下划线的，需要拆分
	var str string
	for i := 0; i < len(temp); i++ {
		b := []rune(temp[i])
		for j := 0; j < len(b); j++ {
			if j == 0 {
				// 首字母大写转换
				b[j] -= 32
				str += string(b[j])
			} else {
				str += string(b[j])
			}
		}
	}

	return str
}

func firstUpper(propName string) string {
	return strings.ToLower(propName[:1]) + propName[1:]
}
